// Copyright 2008 Google Inc.  All Rights Reserved.
/**
 * @fileoverview A wrapper for opensocial javascript API into actionscript.
 * <p>
 * It works with the Opensocial Flash API.
 * In this file, the local var with underscore indicates that this obj is
 * a JSON object wappered from opensocial data types.
 * </p>
 * 
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/
 * @see http://www/~yiziwu/opensocial-flashapi/actionscript/
 * @see http://code.google.com/apis/opensocial/docs/0.7/reference/
 *
 * @version 0.7
 * @author yiziwu@google.com (Yizi Wu)
 */

//======================== FLAGS and Constants =================================
/** 
 * Flag for JSCompiler.
 * @type {boolean}
 * @private
 */
var COMPILED = false;

/** 
 * Set to false for release. 
 * @type {boolean}
 * @private
 */
var DEBUG = true;

//======================== External Namespace ==================================
/**
 * External namespace for <code>gadgets</code> for internal reference.
 * @static
 * @class
 * @name gadgets
 */
var gadgets = window['gadgets'];
if (!gadgets) {
  INFO('gadgets.* not found.');
}


/**
 * External namespace for <code>opensocial</code> for internal reference.
 * @static
 * @class
 * @name opensocial
 */
var opensocial = window['opensocial'];
if (!gadgets) {
  INFO('opensocial.* not found.');
}


/**
 * Namespace for top-level api functions.
 * @static
 * @class
 * @name flashAPI
 */
var flashAPI = {};
// Exports the namespace
// All methods in this namespace is exposed to flash
window['flashAPI'] = flashAPI;



/**
 * The Opensocial Javascript API version.
 * @member flashAPI
 * @type {number}
 */
flashAPI.VERSON = 0.7;

/**
 * The default flash id embeded in container.
 * @member flashAPI
 * @private
 * @type {string}
 */
flashAPI.DEFAULT_FLASH_ID = "flashObj";

//--------------------------- Environment --------------------------------------

/**
 * The flash movie object which apply this API.
 * This object is initialized in <code>flashAPI.isAPIReady</code> function.
 * @member flashAPI
 * @type {HTMLElement}
 */
flashAPI.swfObj;


/**
 * Indicates if the opensocial library is loaded. 
 * Initialize the <code>opensocial</code> and <code>gadgets</code> namespaces.
 * Initialize the <code>flashAPI.swfObj</code> element. 
 *
 * The objectId of the swf is defined in gagdet spec. If the swf is embeded by
 * calling <code>flashAPI.embededFlash</code> function, the detault objectId 
 * will be 'flashAPI.swfObj'.
 *
 * This function is called by the swf's ExternalInterface continuingly until it
 * returns true.
 *
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/API.as
 * @requires opensocial
 * @member flashAPI
 *
 * @param {string?} objectId The object Id passed from the swf.
 * @return {boolean} true if all javascripts are loaded.
 */
flashAPI.isAPIReady = function(objectId) {
  try {
    if (!flashAPI.swfObj) {
      flashAPI.swfObj = flashAPI.thisFlash(objectId);
    }
    if (flashAPI.swfObj) {
      return true; 
    } else {
      return false;
    }
  } catch (e) {
    INFO(e);
    return false;
  }
};


/**
 * Because <code>gadgets.flash.embedFlash</code> cannot auto activate it in IE6,
 * so here we need a special one.
 * @requires gadgets.flash
 * @member flashAPI
 * @param {string} swfUrl  The swf file's location.
 * @param {string} swfContainer  The HTML element id.
 * @param {string?} opt_playerVersion Required flash player version.
 * @param {Object.<string, string>?} opt_params Flash object's parameters.
 * @param {string?} opt_upgradeMsgHTML  
 *        The message if the browser's version needs to be upgraded.
 */
flashAPI.embedFlash = function(swfUrl, swfContainer, opt_playerVersion, 
                               opt_params, opt_upgradeMsgHTML) {
  // Check container element
  var container = document.getElementById(swfContainer);
  if (!container) return false;

  // Check version
  /*var version = opt_playerVersion.match(/(\d+)(?=[\.,]?)/g);
  var expectMajorVer = (version != null ? Number(version[0]) : 9);
  EXPORT("currentVer", gadgets.flash.getMajorVersion());
  EXPORT("expectVer", expectMajorVer);
  
  if (gadgets.flash.getMajorVersion() < expectMajorVer) {
    opt_upgradeMsgHTML = opt_upgradeMsgHTML || 
        '<span style="font-size:small;color:#777;"> ' +
        'Please install or upgrade to ' +
        '<a href="http://www.adobe.com/cn/products/flashplayer/" ' +
        'style="color:#36C;" target="_blank">Flash Player 9.0</a>.</span>';
    container.innerHTML = opt_upgradeMsgHTML;
    return false;
  };*/

  // Generate the flash object element
  var html = "";
  opt_params = opt_params || {};
  if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) {
    // netscape plugin architecture
    html = '<embed src="'+ swfUrl +
           '" id="' + flashAPI.DEFAULT_FLASH_ID + 
           '" name="' + flashAPI.DEFAULT_FLASH_ID + 
           '" type="application/x-shockwave-flash"';
    for(var key in opt_params){ 
      html += key +'="'+ opt_params[key] +'" ';
    }
    html += '/>';
  } else {
    // PC IE
    html = '<object id="' + flashAPI.DEFAULT_FLASH_ID + 
           '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';
    var params = '<param name="movie" value="'+ swfUrl +'" />';
    for(var key in opt_params) {
      if (key == 'width' || key == 'height') {
        html += ' ' + key + '="' + opt_params[key] + '"';
      } else {
        params += '<param name="'+ key +'" value="'+ opt_params[key] +'" />';
      }
    }
    html += '>' + params + "</object>";
  }
  container.innerHTML = html;
  return true;
};


/**
 * Returns the env object for container
 * @requires gadgets.views
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/API.as
 * @member flashAPI
 * @return {Object} a map of all environment values.
 */
flashAPI.getEnvironment = function() {
  var envObj_ = {};
  if (opensocial.getEnvironment) {
    var env = opensocial.getEnvironment();
    envObj_.domain = env.getDomain();
    
    // TODO Add support field check
    
  } else {
    // TODO Here is work around!
    envObj_.domain = flashAPI.extractDomain(document.referrer);
  }
  
  // Default view type
  envObj_.view = 'profile';
    
  if (gadgets) {
    if (gadgets.views) {
      envObj_.view = gadgets.views.getCurrentView().getName();
    }
  }
  EXPORT('env', envObj_);  // for debugging
  return envObj_;
};

flashAPI.getPlatform = function() {
  return "opensocial";
};



/**
 * Set the width for the flash stage
 * Very usefull for dynamic loading and view switching.
 * TODO: need some check!
 * @member flashAPI
 * @param {number} newWidth New width (in pixel) to be applied.
 */
flashAPI.setStageWidth = function(newWidth) {
  if (flashAPI.swfObj.width != newWidth) {
    flashAPI.swfObj.width = newWidth;
  }
};


/**
 * Set the height for the flash stage
 * Very usefull for dynamic loading and view switching
 * TODO: need some check!
 * @requires gadgets.window
 * @member flashAPI
 * @param {number} newHeight New height (in pixel) to be applied.
 */
flashAPI.setStageHeight = function(newHeight) {
  if (flashAPI.swfObj.height != newHeight) {
    flashAPI.swfObj.height = newHeight;
    if (gadgets && gadgets.window && gadgets.window.adjustHeight) {
      gadgets.window.adjustHeight(newHeight);
    }
  }
};


//--------------------- Data requests senders ----------------------------------
/**
 * Sends out the request for basic data set.
 * NOTE:
 * Here we pass JSON string instead of an object to Flash via the callback.
 * See comments of 'handleFetchBasicData' method in the API.as file.
 *
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/API.as
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/BasicData.as
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {Array.<string>} keys The app keys.
 * @param {Object.<string, Object>} params 
 *        A map of PeopleRequestField's names and values.
 */
flashAPI.fetchBasicData = function(reqID, keys, params) {

  EXPORT('params_', params); // for debug

  params = flashAPI.wrapParams(
      params, opensocial.DataRequest.PeopleRequestFields);

  // The params object need to be cloned, because req.add method will modify it!
  var req = opensocial.newDataRequest();
  req.add(req.newFetchPersonRequest('OWNER', 
      flashAPI.cloneObject(params)), 'o');
  req.add(req.newFetchPersonRequest('VIEWER', 
      flashAPI.cloneObject(params)), 'v');
  req.add(req.newFetchPeopleRequest('OWNER_FRIENDS', 
      flashAPI.cloneObject(params)), 'of');
  req.add(req.newFetchPeopleRequest('VIEWER_FRIENDS', 
      flashAPI.cloneObject(params)), 'vf');
  
  // TODO Not support by our container yet
  req.add(req.newFetchPersonAppDataRequest('OWNER', keys), 'od');
  req.add(req.newFetchPersonAppDataRequest('VIEWER', keys), 'vd');

  
  req.send(function(dataResponse) {
    try {
      EXPORT('raw', dataResponse); // for debug

      var basicData_ = new flashAPI.BasicData(dataResponse);

      EXPORT('basic_', basicData_); // for debug
      
      flashAPI.swfObj.handleFetchBasicData(reqID,
          gadgets.json.stringify(basicData_));
    } catch (e) {
      flashAPI.swfObj.handleError(reqID, e);  
    }
  });
};


/**
 * Sends out the request for friends of a person.
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {string} idSpec OWNER | VIEWER | OWNER_FRIENDS | VIEWER_FRIENDS | Id.
 * @param {Object.<string, Object>} params 
 *            A map of PeopleRequestField's names and values.
 */
flashAPI.fetchPeople = function(reqID, idSpec, params) {
  params = flashAPI.wrapParams(
      params, opensocial.DataRequest.PeopleRequestFields);

  var req = opensocial.newDataRequest();
  req.add(req.newFetchPeopleRequest(idSpec, params), 'f');
  req.send(function(dataResponse) {
    try {
      var people_ = flashAPI.getPeople(dataResponse, 'f');
      flashAPI.swfObj.handleFetchPeople(reqID, gadgets.json.stringify(people_));
    } catch (e) {
      flashAPI.swfObj.handleError(reqID, e);
    } 
  });
};


/**
 * Sends out the request for a person. 
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {string} id OWNER | VIEWER | Person Id.
 * @param {Object.<string, Object>} params 
 *        A map of PeopleRequestField's names and values.
 */
flashAPI.fetchPerson = function(reqID, id, params) {
  params = flashAPI.wrapParams(
      params, opensocial.DataRequest.PeopleRequestFields);

  var req = opensocial.newDataRequest();
  req.add(req.newFetchPersonRequest(id, params), 'p');
  req.send(function(dataResponse) {
    try {
      var person_ = flashAPI.getPerson(dataResponse, 'p');
      flashAPI.swfObj.handleFetchPerson(reqID, gadgets.json.stringify(person_));
    } catch (e) {
      flashAPI.swfObj.handleError(reqID, e);  
    } 
  });
};


/**
 * Sends out the request for a person and his/her friends. 
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {string} id OWNER | VIEWER | Person Id.
 * @param {Object.<string, Object>} params 
 *        A map of PeopleRequestField's names and values.
 */
flashAPI.fetchPersonFriends = function(reqID, id, params) {
  params = flashAPI.wrapParams(
      params, opensocial.DataRequest.PeopleRequestFields);

  var req = opensocial.newDataRequest();
  req.add(req.newFetchPersonRequest(id, 
      flashAPI.cloneObject(params)), 'p');
  req.add(req.newFetchPeopleRequest(id, 
      flashAPI.cloneObject(params)), 'f');
  req.send(function(dataResponse) {
    try {
      var person_ = flashAPI.getPerson(dataResponse, 'p');
      var friends_ = flashAPI.getPeople(dataResponse, 'f');
      flashAPI.swfObj.handleFetchPersonFriends(reqID, 
          gadgets.json.stringify(person_),
          gadgets.json.stringify(friends_));
    } catch (e) {
      flashAPI.swfObj.handleError(reqID, e);
    } 
  });
};


/**
 * Sends out the request for personal app data. 
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {string|Array.<string>} idSpec OWNER | VIEWER | Id | Array of Ids.
 * @param {Array.<string>} keys App keys.
 */
flashAPI.fetchPersonAppData = function(reqID, idSpec, keys) {
  var req = opensocial.newDataRequest();
  req.add(req.newFetchPersonAppDataRequest(idSpec, keys), 'd');
  req.send(function(dataResponse) {
    try {
      var dataObj = flashAPI.getData(dataResponse, 'd');
      flashAPI.swfObj.handleFetchPersonAppData(reqID,
          gadgets.json.stringify(dataObj));
    } catch (e) {
      flashAPI.swfObj.handleError(reqID, e);  
    } 
  });
};


/**
 * Sends out the update request for personal app data. 
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {string} id OWNER | VIEWER | Person Id.
 * @param {string} key The data key in this app.
 * @param {Object} value New value.
 */
flashAPI.updatePersonAppData = function(reqID, id, key, value) {
  var req = opensocial.newDataRequest();
  req.add(req.newUpdatePersonAppDataRequest(id, key, value));
  req.send(function(dataResponse) {
    // TODO: deal with error
    flashAPI.swfObj.handleUpdatePersonAppData(reqID);
  });
};

/**
 * Sends out the request for personal activities.
 * @member flashAPI
 * 
 * @param {string} reqID The request id
 * @param {string} id OWNER | VIEWER | Person Id
 * @param {Object.<ActivityRequestField.name, Object>} params 
 *            A map of ActivityRequestField's names and values
 */
flashAPI.fetchActivities = function(reqID, id, params) {
  params = flashAPI.wrapParams(
      params, opensocial.DataRequest.ActivityRequestFields);

  var req = opensocial.newDataRequest();
  req.add(req.newFetchActivitiesRequest(id, params), 'a');
  req.send(function(dataResponse) {
    try {
      var activities_ = flashAPI.getActivities(dataResponse, 'a');
      flashAPI.swfObj.handleFetchActivities(reqID, 
          gadgets.json.stringify(activities_));
    } catch (e) {
      flashAPI.swfObj.handleError(reqID, e);  
    } 
  });
};

/**
 * Sends out the update request for new activity. 
 * @member flashAPI
 * 
 * @param {string} reqID The request id.
 * @param {Object.<string, Object>} params
 *        A map of ActivityRequestField's names and values.
 * @param {string} priority HIGH | LOW.
 */
flashAPI.createActivity = function(reqID, params, priority) {
  params = flashAPI.wrapParams(
      params, opensocial.DataRequest.ActivityRequestFields);

  var activity = opensocial.newActivity(params);
  opensocial.requestCreateActivity(activity, priority, function(responseItem) {
    // TODO: deal with error
    flashAPI.swfObj.handleCreateActivity(reqID);
  });
};


//--------------------- Basic data structure -----------------------------------
/**
 * The basic data set structure.
 * @member flashAPI
 *
 * @class
 * @constructor
 * @param {opensocial.DataResponse} dataResponse 
 *        The response object from container.
 */
flashAPI.BasicData = function(dataResponse) {
  /** 
   * @member flashAPI.BasicData
   * @type {flashAPI.Person?}
   */
  this.viewer = flashAPI.getPerson(dataResponse, 'v');
  /**
   * @member flashAPI.BasicData
   * @type {Object.<string, string>?}
   */
  this.viewerData = flashAPI.getPersonAppData(dataResponse, 'vd', 'VIEWER');
  /**
   * @member flashAPI.BasicData
   * @type {flashAPI.Collection?}
   */
  this.viewerFriends = flashAPI.getPeople(dataResponse, 'vf');
  /** 
   * @member flashAPI.BasicData
   * @type {flashAPI.Person?}
   */ 
  this.owner = flashAPI.getPerson(dataResponse, 'o');
  /**
   * @member flashAPI.BasicData
   * @type {Object.<string, string>?}
   */
  this.ownerData = flashAPI.getPersonAppData(dataResponse, 'od', 'OWNER');
  /**
   * @member flashAPI.BasicData
   * @type {flashAPI.Collection?}
   */
  this.ownerFriends = flashAPI.getPeople(dataResponse, 'of');
};


//--------------------- Kernel type wrappers --------------------------------
/**
 * The collection class, wrapped from <code>opensocial.Collection</code>.
 * @member flashAPI
 * @class 
 * @constructor
 * @param {opensocial.Collection} collection The original data object.
 * @param {Function} flashAPIType 
 *        The constuctor of the items in this collection.
 */
flashAPI.Collection = function(collection, flashAPIType) {
  /**
   * All the objects in this collection.
   * @member flashAPI.Collection
   * @type {Array}
   */
  this.array = [];

  /** 
   * The total size of the larger result set that this collection belongs to.
   * @member flashAPI.Collection
   * @type {number}
   */
  this.totalSize = 0;
  
  /**
   * The size of this collection, which is equal to or less than the total size 
   * of the result.
   * @member flashAPI.Collection
   * @type {number}
   */
  this.size = 0;
  
  /**
   * The offset of this collection within a larger result set.
   * @member flashAPI.Collection
   * @type {number}
   */
  this.offset = 0;

  // Initialization
  if (!collection) return;
  var array = null;
  if (collection.asArray) { // due with real Collection
    array = collection.asArray();
    this.offset = collection.getOffset();
    this.totalSize =  collection.getTotalSize();
    this.size = collection.size();
  } else if (flashAPI.isArray(collection)){ // due with array
    array = collection;
    this.offset = 0;
    this.totalSize = collection.length;
    this.size = this.totalSize;
  }
  if (array != null) {
    for (var index in array) {
      this.array.push(new flashAPIType(array[index]));
    }
  }
};


/**
 * The person class wrapped from <code>opensocial.Person</code>.
 * @member flashAPI
 * @class
 * @constructor
 * @param {opensocial.Person} person The original data object.
 */
flashAPI.Person = function(person) {
  if (!person) return;

  /**
   * True if this person is the OWNER
   * @member flashAPI.Person
   * @type {boolean}
   */
  this.isOwner = person.isOwner();

  /**
   * True if this person is the VIEWER
   * @member flashAPI.Person
   * @type {boolean}
   */
  this.isViewer = person.isViewer();

  /**
   * The person's meaningful name for displaying.
   * @member flashAPI.Person
   * @type {string}
   */
  this.displayName = person.getDisplayName();

  /**
   * The person id.
   * @member flashAPI.Person
   * @type {string}
   */
  this.id = person.getId();
  
  /**
   * The person's profile fields.
   * @member flashAPI.Person
   * @type {Object.<string, string|Array|Object>}
   */
  this.fields = {};
  flashAPI.wrapFields(person, this);
};


/**
 * The activity class wrapped from <code>opensocial.Activity</code>.
 * TODO: setField not supported.
 * @member flashAPI
 * @class
 * @constructor
 * @param {opensocial.Activity} activity The original data object.
 *
 */
flashAPI.Activity = function(activity) {
  if (!activity) return;

  /**
   * The activity id.
   * @member flashAPI.Activity
   * @type {string}
   */  
  this.id = activity.getId();
  
  /**
   * The activity content fields.
   * @member flashAPI.Activity
   * @type {Object.<string, string|Array|Object>}
   */
  this.fields = {};
  flashAPI.wrapFields(activity, this);
};


/**
 * The message class wrapped from <code>opensocial.Message</code>.
 * TODO: setField not supported.
 * @member flashAPI
 * @class
 * @constructor
 * @param {opensocial.Message} message The original data object.
 *
 */
flashAPI.Message = function(message) {
  if (!message) return;

  /**
   * The message contents.
   * @member flashAPI.Message
   * @type {Object.<string, string|Array|Object>}
   */
  this.fields = {};
  flashAPI.wrapFields(message, this);
};

/**
 * Wrapped class for all other data types in <code>opensocial</code> namespace.
 * @member flashAPI
 * @class
 * @constructor
 * @param {Object?} opt_data
 *        The original data object, can be <code>opensocial.Email</code>, 
 *        <code>opensocial.Url</code>, etc.
 */
flashAPI.SimpleType = function(opt_data) {
  /**
   * The data fields.
   * @member flashAPI.SimpleType
   * @type {Object.<string, string|Array|Object>}
   */
  this.fields = {};
  
  if (opt_data != null) {
    flashAPI.wrapFields(opt_data, this);
  }
};


//======================== Internal helper methods =============================
// All following functions are only used in this javascript file.
// (JSCompiler will rename the names!)

//--------------------- Data response accessors --------------------------------
/**
 * Gets data object from resoponse.
 * @member flashAPI
 * @private
 * @param {opensocial.DataResponse} dataResponse The response object.
 * @param {string} key The data key in the response object.
 * @throws {Error} When the RPC meets error.
 * @return {Object?} The data, can be any instance of the opensocial data type,
 *                   or a collection data type.
 */
flashAPI.getData = function(dataResponse, key) {
  if (!dataResponse) return null;
  if (dataResponse.hadError()) {
    console.info('[ERROR] Data Response Error!');
  }
  if (!key) return null;
  var responseItem = dataResponse.get(key);
  if (!responseItem) return null;
  if (responseItem.hadError()) {
    console.info('[ERROR] ' + responseItem.getErrorCode() + ':' + 
                    responseItem.getErrorMessage());
  }
  return responseItem.getData();
};


/**
 * Gets person from the response.
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/Person.as
 * @member flashAPI
 * @private
 * @param {opensocial.DataResponse} dataResponse The response object.
 * @param {string} key The data key in the response object.
 * @return {flashAPI.Person?} 
 *         Wrapped JSON object of an <code>opensocial.Person</code> object.
 */
flashAPI.getPerson = function(dataResponse, key) {
  var data = flashAPI.getData(dataResponse, key);

  if (key == 'v') {
    EXPORT('viewer', data);  // for debug
  } else if (key == 'o') {
    EXPORT('owner', data);  // for debug
  }
  
  if (!data) return null;
  var person_ = new flashAPI.Person(data);
  return person_;
};

/**
 * Gets multiple people from the response.
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/Person.as
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/Collection.as
 * @member flashAPI
 * @private
 * @param {opensocial.DataResponse} dataResponse The response object.
 * @param {string} key The data key in the response object.
 * @return {flashAPI.Collection?} 
 *         Wrapped JSON object of an <code>opensocial.Collection</code> object.
 */
flashAPI.getPeople = function(dataResponse, key) {
  var data = flashAPI.getData(dataResponse, key);
  if (!data) return null;
  var people_ = new flashAPI.Collection(data, flashAPI.Person);
  return people_;
};

/**
 * Gets the user-scope app data from response. The app data is a map with
 * the keys are the person ids.
 * @member flashAPI
 * @private
 * @param {opensocial.DataResponse} dataResponse The response object.
 * @param {string} key The data key in the response object.
 * @param {string?} personId The person Id.
 * @return {Object.<string, string>?} 
 *         The key-value pairs for the person in this app.
 */
flashAPI.getPersonAppData = function(dataResponse, key, personId) {
  var data = flashAPI.getData(dataResponse, key);
  if (!data) return null;
  if (personId == 'VIEWER' || personId == 'OWNER') {
    for (var id in data) {
      return data[id];
    }
  } else if (personId != null){
    return data[personId];
  }
  return null;
};

/**
 * Gets multiple activities from the response.
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/Activity.as
 * @see //flash/actionscript/com/google/ccc/chinasocial/api/Collection.as
 * @member flashAPI
 * @private
 * @param {opensocial.DataResponse} dataResponse The response object.
 * @param {string} key The data key in the response object.
 * @return {flashAPI.Collection?}
 *         Wrapped JSON object of an <code>opensocial.Collection</code> object.
 */
flashAPI.getActivities = function(dataResponse, key) {
  var data = flashAPI.getData(dataResponse, key);
  if (!data) return null;
  var activities_ = new flashAPI.Collection(data, flashAPI.Activity);
  return activities_;
};


//--------------------- Simple type wrappers ---------------------------------
/**
 * Checks the responded opensocial data structure's type.
 * @member flashAPI
 * @private
 * @param {Object?} dataObj The original opensocial data object.
 * @return {Function?} the types' constructor.
 */
flashAPI.checkType = function(dataObj) {
  var type = null;
  // TODO: MediaItem and BodyType are not supported yet.
  if (opensocial.Address && dataObj instanceof opensocial.Address) {
    type = opensocial.Address;
  } else if (opensocial.Email && dataObj instanceof opensocial.Email) {
    type = opensocial.Email;
  } else if (opensocial.Name && dataObj instanceof opensocial.Name) {
    type = opensocial.Name;
  } else if (opensocial.Organization && 
             dataObj instanceof opensocial.Organization) {
    type = opensocial.Organization;
  } else if (opensocial.Phone && dataObj instanceof opensocial.Phone) {
    type = opensocial.Phone;
  } else if (opensocial.Url && dataObj instanceof opensocial.Url) {
    type = opensocial.Url;
  } else if (opensocial.Person && dataObj instanceof opensocial.Person) {
    type = opensocial.Person;
  } else if (opensocial.Activity && dataObj instanceof opensocial.Activity) {
    type = opensocial.Activity;
  } else if (opensocial.Message && dataObj instanceof opensocial.Message) {
    type = opensocial.Message;
  } else if (opensocial.Enum && dataObj instanceof opensocial.Enum) {
    type = opensocial.Enum;
  }
  return type;
};


/**
 * Wraps the data objects fields to JSON-style objects without any 
 * getter/setter functions. Because in actionscript's ExternalInterface cannot 
 * use the getFields to get the data correctly.
 * @member flashAPI
 * @private
 * @param {Object?} dataObj The opensocial data, 
 *        e.g. <code>opensocial.Email</code>, <code>opensocial.Person</code>...
 * @param {Object?} opt_this The converted obj for flash.
 * @return {Object?} the converted obj for flash.
 */
flashAPI.wrapFields = function(dataObj, opt_this) {
  if (dataObj == null) return null;
  var type = flashAPI.checkType(dataObj);

  if (type == null) {
    // treat it as string
    return dataObj;
  } else if (type == opensocial.Enum) {
    // return enum name
    return dataObj.getDisplayValue();
  }

  // Null parameter for preventing extra recursion.
  opt_this = opt_this || new flashAPI.SimpleType(null);    

  opt_this.fields = opt_this.fields || {};

  // Grabs the fields data from each structures recursively.
  for (var k in type.Field) {
    var key = type.Field[k];
    var value = dataObj.getField(key);
    if (value != null) {
      if (flashAPI.isArray(value)) {
        opt_this.fields[k] = [];
        flashAPI.forEach(value, function(item) {
          opt_this.fields[k].push(flashAPI.wrapFields(item));
        });
      } else {
        opt_this.fields[k] = flashAPI.wrapFields(value);
      }
    }
  }

  return opt_this || {};
};


/**
 * Wraps JOSN-style (raw) params object (from actionscript) keys to opensocial 
 * field types.
 * TODO: Maybe more field types in the futures. Not neatly imeplemtation here.
 * @member flashAPI
 * @private
 * @param {Object.<string, Object>?} rawParams 
 *        The params object from actionscript.
 * @param {Function?} type 
 *        The field type class constructor.
 *        e.g. <code>opensocial.DataRequest.PeopleRequestFields</code>.
 * @return {Object.<Object, Object>?} New params with the correct keys.
 */
flashAPI.wrapParams = function(rawParams, type) {
  if (!type || !rawParams) return null;
  var params = {};
  for (var strKey in rawParams) {
    switch (strKey) {
      case 'PROFILE_DETAILS':
        var fieldNames = rawParams[strKey];
        if (fieldNames != null) {
          var fieldArray = [];
          // Convert string array to request field objects array
          for (var i = 0; i < fieldNames.length; ++i) {
            fieldArray.push(opensocial.Person.Field[fieldNames[i]]);
          }
          params[type[strKey]] = fieldArray;
        }
        break;
      case 'FILTER':
        params[type[strKey]] = 
            opensocial.DataRequest.FilterType[rawParams[strKey]];
        break;
      case 'SORT_ORDER':
        params[type[strKey]] = 
            opensocial.DataRequest.SortOrder[rawParams[strKey]];
        break;
      default:
        params[type[strKey]] = rawParams[strKey];
        break;
    }
  }
  return params;
}


//--------------------- Utilities ----------------------------------------------
/**
 * Checks if the object is an array.
 * @member flashAPI
 * @private
 * @param {Object} obj object to be check.
 * @return {boolean} True if the object is an array.
 */
flashAPI.isArray = function(obj) {
  if (!obj) return false;
  if (Array) {
    return obj instanceof Array;
  } else {
    if ((typeof obj == 'object') && 
        (typeof obj.length == 'number') && 
        (typeof obj.splice != 'undefined')) {
      return true;
    } else {
      return false;
    }
  }
};

/**
 * Iterates an array, calls the function for each item.
 * @member flashAPI
 * @private
 * @param {Array} array The array to be iterated.
 * @param {Function} f The function to be applied.
 * @param {Object} opt_thisObj The this object for the function.
 */
flashAPI.forEach = function(array, f, opt_thisObj) {
  var l = array.length;
  for (var i = 0; i < l; i++) {
    if (i in array) {
      f.call(opt_thisObj, array[i], i, array);
    }
  }
};

/**
 * Extracts container domain name from url(Referer). e.g. sandbox.orkut.com
 * @member flashAPI
 * @private
 * @param {string} url The request url.
 * @return {string} The domain.
 */
flashAPI.extractDomain = function(url) {
  var pattern = /(\w+):\/\/([\w.]+)\/(\S*)/;
  var result = url.match(pattern);
  if (result != null) {
    return result[2];
  } else {
    return '';
  }
};

/**
 * Deeply clones an object.
 * @member flashAPI
 * @private
 * @param {Object?} obj The original object.
 * @return {Object?} The cloned object.
 */
flashAPI.cloneObject = function(obj) {
  if(obj == null) return null;
  switch (typeof obj){
    case 'function':
      return obj;
    case 'object': 
      var newObj = new obj.constructor();
      for(var key in obj) newObj[key] = flashAPI.cloneObject(obj[key]);
      return newObj;
    default:
      return obj.valueOf();
  }
};

/**
 * Get the swf object.
 * @member flashAPI
 * @private
 * @param {string?} name The <object>/<embed> tag's id or name
 * @return {HTMLElement} the swf object.
 */
flashAPI.thisFlash = function(name) {
  name = name || flashAPI.DEFAULT_FLASH_ID;
  if (navigator.appName.indexOf('Microsoft') != -1) {
    return document[name];
  } else {
    return document.getElementById(name);
  }
};

/**
 * For debugging, return the object's keys and values.
 * @member flashAPI
 * @private
 * @param {Object?} obj Any object to be looked into.
 * @param {string?} opt_prefix The prefix string of each output line.
 * @param {string?} opt_rendered The rendered string passing between recursions.
 * @return {string?} A tree-style string to represent the object.
 */
flashAPI.debugInspect = function(obj, opt_prefix, opt_rendered) {
  if (!opt_rendered) opt_rendered = '\n';
  if (!opt_prefix) opt_prefix = '';
  if (obj == null) return opt_rendered || "";
  for (var k in obj) {
    opt_rendered += 
        (opt_prefix + k + '[' + (typeof obj[k]) + '] : ' + obj[k] + '\n');
    if ((typeof obj[k]) == 'object') {
      opt_rendered = 
          flashAPI.debugInspect(obj[k], opt_prefix + '\t', opt_rendered);
    }
  }
  return opt_rendered || "";
};


/**
 * For debugging, export the local variables to global.
 * Only available when DEBUG = true.
 * @member flashAPI
 * @private
 * @param {string} name The name.
 * @param {Object?} value The value.
 */
flashAPI.debugExport = function(name, value) {
  if (DEBUG) {
    window['debug'] = window['debug'] || {};
    window['debug'][name] = value;
  }
};

/**
 * For debugging, output text information to firebug console.
 * Only available when DEBUG = true.
 * @member flashAPI
 * @private
 * @param {Object?} content The content to be logged.
 */
flashAPI.debugInfo = function(content) {
  if (DEBUG) {
    console.info(content);
  }
};

// Export these debugging functions for shortcuts
window["INFO"] = flashAPI.debugInfo;
window["INSPECT"] = flashAPI.debugInspect;
window["EXPORT"] = flashAPI.debugExport;

